/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief PNG encoder interface using the libpng library.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#include "PNGEncoder.hpp"
#include "owndebug.h"
#include "ownutils.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>

//******************************************************************************
//******************************************************************************
#define PNGDECODER_RGBBUFFER_BLOCKSIZE 1024
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

// if (setjmp(png_jmpbuf(png_ptr)))

CPNGEncoder::CPNGEncoder()
  : iPNGData(NULL),
    iPNGDataSize(0),
    iPNGDataReserved(0),
    iRawData(NULL),
    iRawDataSize(0),
    iRawDataReserved(0),
    iRawDataOwned(false),
    iPNGEncoderPtr(NULL),
    iPNGInfoPtr(NULL),
    iGotPNG(false),
    iWidth(-1),
    iHeight(-1),
    iColorType(PNG_COLOR_TYPE_RGB),
    iBitDepth(8),
    iCompressionLevel(Z_BEST_SPEED),
    iBytesPerPixel(3),
    iWriteDataIndex(0)
{
}
//******************************************************************************

CPNGEncoder::~CPNGEncoder()
{
  // Delete the RGB data (if allocated)
  if (iRawData && iRawDataOwned) {
    delete[] iRawData;
    iRawData = NULL;
  }

  // Delete PNG data (if allocated by us)
  if (iPNGData) {
    delete[] iPNGData;
    iPNGData = NULL;
  }

  // Delete png encoder.
  if (iPNGEncoderPtr) {
    png_destroy_write_struct(&iPNGEncoderPtr,&iPNGInfoPtr);
    iPNGEncoderPtr = NULL;
    iPNGInfoPtr = NULL;
  }
}
//******************************************************************************

bool CPNGEncoder::SetRGBDataPtr(const char *aData, const int aDataLen, 
                                const unsigned int aWidth, const unsigned int aHeight, 
                                bool aCopy)
{
  return SetRawDataPtr(aData, aDataLen, aWidth, aHeight, PNG_COLOR_TYPE_RGB, 8, 3, aCopy); 
}
//******************************************************************************

bool CPNGEncoder::SetRawDataPtr(const char *aData, const int aDataLen, 
                                const unsigned int aWidth, const unsigned int aHeight, 
                                const unsigned int aColorType, 
                                const unsigned int aBitDepth,
                                const unsigned int aBytesPerPixel,
                                bool aCopy) 
                                
{
  // Store dimensions
  iWidth = aWidth;
  iHeight = aHeight;

  // set parameters
  if (!SetEncodingParameters(aColorType, aBitDepth, aBytesPerPixel)) {
    dPrint(ODERROR,"Invalid encoding parameters! (ColorType: %u, Depth: %u, BPP: %u)",
           iColorType, iBitDepth, iBytesPerPixel);
    return false;
  }

  // Is Copy requested?
  if (aCopy) {
    // If data was not previously owned, clear out pointer to allow
    // proper allocation.
    if (!iRawDataOwned) {
      if (iRawData) iRawData = NULL;
    }
    
    // Copy requested, so copy the memory region pointed.
    iRawDataOwned = true;
    iRawDataSize = aDataLen;
    ReserveRawData(aDataLen);
    memcpy(iRawData, aData, aDataLen);

  } else {
    // No copy; hence free the possible existing memory block.
    if (iRawDataOwned && iRawData) {
      delete[] iRawData;
      iRawDataReserved = 0;
    }

    // No copy requested; assign pointer only.
    iRawDataOwned = false;
    iRawData = const_cast<char *>(aData);
    iRawDataSize = aDataLen;
  }

  // Got Pointers, call to set up initial Encode.
  return InitEncode();
}
//******************************************************************************

void CPNGEncoder::ReservePNGData(const unsigned int aSizeBytes)
{
  // Buffer always owned
  if (iPNGDataReserved < aSizeBytes) {
    if (iPNGData) delete[] iPNGData;
    iPNGDataReserved = aSizeBytes;
    iPNGData = new char [aSizeBytes];
  }
}
//******************************************************************************

void CPNGEncoder::ReserveRawData(const unsigned int aSizeBytes)
{
  // Buffer must be owned prior call.
  assert(iRawDataOwned == true);
  if (iRawDataReserved < aSizeBytes) {
    if (iRawData) delete[] iRawData;
    iRawDataReserved = aSizeBytes;
    iRawData = new char [aSizeBytes];
  }
}
//******************************************************************************

bool CPNGEncoder::InitEncode()
{
  bool result = false;

  // Check pointers and size for status.
  if (iRawData && iRawDataSize >= (iWidth * iHeight * iBytesPerPixel)) {
    // IF pointer is valid, destroy it first. (FIXME: Is it possible to reuse?)
    if (iPNGEncoderPtr) {
      png_destroy_write_struct(&iPNGEncoderPtr,&iPNGInfoPtr);
      iPNGEncoderPtr = NULL;
      iPNGInfoPtr = NULL;
    }
    
    // get PNG file info struct (memory is allocated by libpng)
    iPNGEncoderPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (iPNGEncoderPtr) {
      // get PNG image data info struct (memory is allocated by libpng)
      iPNGInfoPtr = png_create_info_struct(iPNGEncoderPtr);
      
      if(iPNGInfoPtr) {
        // Set compression
        png_set_compression_level(iPNGEncoderPtr, iCompressionLevel);
        
        // Register CallBack.
        png_set_write_fn(iPNGEncoderPtr, this, WriteDataToOutputStream, FlushDataToOutputStream);
        
        // Well - now we should be set! InitEncode OK.
        result = true;
        
      } else {
        // libpng must free file info struct memory before we bail
        png_destroy_write_struct(&iPNGEncoderPtr,&iPNGInfoPtr);
        
      }
    }     
  }
  
  // If result was FALSE, mark datasize as zero. (following phases
  // will fail immediately if attempted)
  if (!result) iRawDataSize = 0;
 
  return result;
}
//******************************************************************************

bool CPNGEncoder::Encode()
{
  bool result = false;

  if (iRawData && iRawDataSize > 0) {

    // Mark GotPNG as false.
    iGotPNG = false;
    iPNGDataSize = 0;
    
    // Will allocate memory as much as raw data (should fit)
    ReservePNGData(MAX(iRawDataSize, 4096));
    iWriteDataIndex = 0; // Begin from zero index.

    // Fill in basic information.
    png_set_IHDR(iPNGEncoderPtr, iPNGInfoPtr,
                 iWidth, iHeight, iBitDepth, iColorType, PNG_INTERLACE_NONE,
                 PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);
    
    // Row pointers.
    png_byte *row_pointers[iHeight];
    for(unsigned int i=0; i < iHeight; ++i) {
      row_pointers[i] = (png_byte*) iRawData + (i*(iWidth * iBytesPerPixel));
    }
    
    // Call writes!
    png_write_info(iPNGEncoderPtr, iPNGInfoPtr);
    png_write_image(iPNGEncoderPtr, row_pointers);
    png_write_end(iPNGEncoderPtr, iPNGInfoPtr);
    
    // Check
    assert(iPNGDataSize > 0);
    
    // FIXME
    result = iGotPNG = true;
  }

  // Return result.
  return result;
}
//******************************************************************************

int CPNGEncoder::GetImageWidth(void) 
{
  return iWidth;
}
//******************************************************************************

int CPNGEncoder::GetImageHeight(void)
{
  return iHeight;
}
//******************************************************************************

const char *CPNGEncoder::GetPNGDataPtr(void)
{
  return iGotPNG ? iPNGData : NULL;
}
//******************************************************************************

bool CPNGEncoder::GetPNGDataCopy(char *aDataPtr, const unsigned int aMaxBufSize)
{
  bool result = false;
  const char *ptr = GetPNGDataPtr();
  if (ptr && aMaxBufSize >= iPNGDataSize) {
    memcpy(aDataPtr, iPNGData, iPNGDataSize);
    result = true;
  }
  return result;
}
//******************************************************************************

int CPNGEncoder::GetPNGDataSize(void)
{
  return iPNGDataSize;
}
//******************************************************************************

bool CPNGEncoder::SetEncodingParameters(const unsigned int aColorType,
                                        const unsigned int aBitDepth,
                                        const unsigned int aBPP)
{
  bool result = false;
  if (aColorType == PNG_COLOR_TYPE_GRAY && 
      (aBitDepth == 1 || aBitDepth == 2 || aBitDepth == 4 || aBitDepth == 8 || aBitDepth == 16)) {
    result = true;
  
  } else if (aColorType == PNG_COLOR_TYPE_GRAY_ALPHA &&
             (aBitDepth == 8 || aBitDepth == 16)) {
    result = true;
    
  } else if (aColorType == PNG_COLOR_TYPE_PALETTE &&
             (aBitDepth == 1 || aBitDepth == 2 || aBitDepth == 4 || aBitDepth == 8)) {
    result = true;
    
  } else if (aColorType == PNG_COLOR_TYPE_RGB &&
             (aBitDepth == 8 || aBitDepth == 16)) {
    result = true;
    
  } else if (aColorType == PNG_COLOR_TYPE_RGB_ALPHA &&
             (aBitDepth == 8 || aBitDepth == 16)) {
    result = true;
    
  }
  
  if (result) {
    iColorType = aColorType;
    iBitDepth = aBitDepth;
    iBytesPerPixel = aBPP;
  }
  
  return result;

}
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

void CPNGEncoder::WriteDataToOutputStream(png_structp png_ptr, 
                                          png_bytep outBytes,
                                          png_size_t byteCountToWrite)
{
  assert(png_ptr->io_ptr != NULL);
  
  // Convert pointer to class.
  CPNGEncoder *c = (CPNGEncoder *)png_ptr->io_ptr;

  // Check buffer size, resize if required.
  const unsigned int new_size = c->iWriteDataIndex + byteCountToWrite; 
  if (c->iPNGDataReserved < new_size) {
    const unsigned int rnewsize = MAX(new_size<<1, 4096);
    char *nptr = new char [rnewsize];
    assert(nptr);
    memcpy(nptr, c->iPNGData, c->iPNGDataSize);
    c->iPNGDataReserved = rnewsize;
    delete[] c->iPNGData;
    c->iPNGData = nptr;
  }


  // Verify
  assert(c->iPNGDataReserved >= c->iWriteDataIndex + byteCountToWrite);

  // Copy the data.
  memcpy(c->iPNGData+c->iWriteDataIndex, outBytes, byteCountToWrite);

  // Increment index by written amount
  c->iWriteDataIndex += byteCountToWrite;

  // Update DataSize (matches the iWriteDataIndex)
  c->iPNGDataSize = c->iWriteDataIndex;
}
//******************************************************************************

void CPNGEncoder::FlushDataToOutputStream(png_structp png_ptr)
{
  // Do nada
}
//******************************************************************************
//******************************************************************************
